package cc.lj.internal.payment.infrastructure.interceptor;

import cc.lj.internal.core.utils.TransitionUtil;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.util.List;
import java.util.Properties;

/**
 *  自定义 mybatis 拦截器,　这里之只拦截insert 和 update
 *　拦截StatementHandler类中参数类型为Statement的 prepare 方法
 * 即拦截 Statement prepare(Connection var1, Integer var2) 方法
 * 目的是：为了处理mybatis参数类型为具体的实体类，然后将参数属性值转码为latin1编码
 * 解决mysql数据为latin1编码问题
 * 注意：该处理方式要求连接mysql的jdbc的url必须是：characterEncoding=latin1
 * @author liangxifeng
 * @date 2022-01-11
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
@Component
@Slf4j
public class MybatisParamInterceptor implements Interceptor {
    @Autowired
    private Environment environment;
    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //读取配置文件中的开关
        String isEnable = environment.getProperty("ljconfig.mybatis.interceptor.param-enable");
        //如果该拦截器关闭，则直接返回,不做任何处理
        if(isEnable.trim().equals("false")) {
            log.info("mybatis参数拦截器开关是关闭状态.....");
            return invocation.proceed();
        }
        StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);


        // 先判断是不是update 和 insert操作 不是直接过滤
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        if (!SqlCommandType.UPDATE.equals(mappedStatement.getSqlCommandType()) && !SqlCommandType.INSERT
                .equals(mappedStatement.getSqlCommandType()))
        {
            return invocation.proceed();
        }

        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        //入参
        Object parameterObject = boundSql.getParameterObject();

        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        convertLatin1Code(parameterObject,parameterMappings);
        return invocation.proceed();
    }

    /**
     * 将参数从utf8转码
     * sele为latin1
     * @param parameterObject
     * @param parameterMappings
     */
    private static void convertLatin1Code(Object parameterObject,List<ParameterMapping> parameterMappings)
    {
        StringBuffer noteStr = new StringBuffer("mybatis处理sql拦截器，拦截后转码latin1后的值=");
        for(ParameterMapping parameterMapping:parameterMappings)
        {
            //获取参数属性名
            String property = parameterMapping.getProperty();
            MetaObject metaObject = MetaObject.forObject(parameterObject, DEFAULT_OBJECT_FACTORY,
                    DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
            //获取参数属性值
            Object val = metaObject.getValue(property);
            if (val != null && val instanceof String)
            {
                //转码参数属性值为latin1
                val = TransitionUtil.utf8ToLatin1(val.toString());
                noteStr.append(val);
                metaObject.setValue(property, val);
            }
        }
        log.info(noteStr.toString());
    }
    /**
     * 生成拦截对象的代理
     * @param target 目标对象
     * @return 代理对象
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    /**
     * mybatis配置的属性
     * @param properties mybatis配置的属性
     */
    @Override
    public void setProperties(Properties properties) {

    }
}
